package com.own.component.elastic.config;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.own.component.common.exception.BusinessSimpleException;
import com.own.component.elastic.property.ElasticProperty;
import com.own.component.elastic.util.JavaTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClient;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;

/**
 * ElasticAutoConfig
 *
 * @author chenxueli
 * @date 2021-09-22 10:11:00
 */
@Slf4j
@Component
public class ElasticAutoConfig {

    private static final ResourcePatternResolver RESOURCE_RESOLVER = new PathMatchingResourcePatternResolver();

    @Resource
    private ElasticsearchProperties elasticsearchProperties;
    @Resource
    private ElasticProperty elasticProperty;

    /**
     * 获取资源
     *
     * @param location 资源路径
     * @return 资源信息
     */
    public org.springframework.core.io.Resource getResources(String location) {
        try {
            return RESOURCE_RESOLVER.getResource(location);
        } catch (Exception e) {
            throw new BusinessSimpleException("获取证书文件异常");
        }
    }

    /**
     * 生成证书
     *
     * @return 证书信息
     */
    private Certificate generateCertificate() {
        try {
            var factory = CertificateFactory.getInstance("X.509");
            if (elasticProperty.getSslCrtPath().startsWith("/")) {
                return factory.generateCertificate(new FileInputStream(elasticProperty.getSslCrtPath()));
            }
            var resource = getResources(elasticProperty.getSslCrtPath());
            return factory.generateCertificate(resource.getInputStream());
        } catch (Exception e) {
            throw new BusinessSimpleException("搜索引擎证书读取失败");
        }
    }

    /**
     * 生成密钥库文件
     *
     * @return 密钥库文件
     */
    private File generateKeyStoreFile() {
        try {
            var password = elasticProperty.getSslKeyStorePassword().toCharArray();
            var keyStoreName = "keystore.jks";
            var file = new File(keyStoreName);
            if (file.isFile()) {
                return file;
            }
            var certificate = generateCertificate();
            var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            keyStore.setCertificateEntry("es-http-public", certificate);
            keyStore.store(new FileOutputStream(keyStoreName), password);
            return new File(keyStoreName);
        } catch (Exception e) {
            throw new BusinessSimpleException("生成密钥库文件失败");
        }
    }

    /**
     * 获取ssl证书的信息
     *
     * @return sslContext
     */
    private SSLContext getSslContext() {
        // 如果需要添加 SSL 证书验证
        if (elasticProperty.getSslEnabled()) {
            try {
                var keyStoreFile = this.generateKeyStoreFile();
                return SSLContexts.custom()
                        .loadTrustMaterial(keyStoreFile, elasticProperty.getSslKeyStorePassword().toCharArray(), new TrustSelfSignedStrategy())
                        .build();
            } catch (Exception e) {
                throw new BusinessSimpleException("SSL证书加载失败");
            }
        }
        return null;
    }

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        var restClientBuilder = RestClient.builder(
                HttpHost.create(elasticsearchProperties.getUris().getFirst())
        );
        if (StringUtils.isNotEmpty(elasticsearchProperties.getUsername()) && StringUtils.isNotEmpty(elasticsearchProperties.getPassword())) {
            var provider = new BasicCredentialsProvider();
            provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(elasticsearchProperties.getUsername(), elasticsearchProperties.getPassword()));
            restClientBuilder.setHttpClientConfigCallback(httpClientBuilder -> {
                var builder = httpClientBuilder
                        .disableAuthCaching()
                        .setDefaultCredentialsProvider(provider);
                // 获取ssl上下文
                var sslContext = getSslContext();
                return sslContext == null
                        ? builder
                        : builder.setSSLContext(sslContext).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
            });
        }
        // 使用Jackson映射器创建传输层
        var mapper = new ObjectMapper();
        mapper.registerModule(JavaTimeSerializer.MODULE);
        var transport = new RestClientTransport(
                restClientBuilder.build(), new JacksonJsonpMapper(mapper)
        );
        // 创建API客户端
        return new ElasticsearchClient(transport);
    }

}
